package cn.redsoft.wechatapi.menu;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import net.sf.json.JSONException;
import net.sf.json.JSONObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.redsoft.wechatapi.common.Consts;
import cn.redsoft.wechatapi.dto.AccessToken;

/**
 * 公众平台通用接口工具类
 * 
 * @author liuyq
 * @date 2013-08-09
 */
public class MenuUtils {
	private static Logger log = LoggerFactory.getLogger(MenuUtils.class);
	// 菜单创建（POST） 限100（次/天）

	public static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
	public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

	/**
	 * 发起https请求并获取结果
	 * 
	 * @param requestUrl
	 *            请求地址
	 * @param requestMethod
	 *            请求方式（GET、POST）
	 * @param formData
	 *            提交的数据
	 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
	 */
	public static JSONObject httpRequest(String requestUrl,
			String requestMethod, String formData) {
		JSONObject jsonObject = null;
		StringBuffer buffer = new StringBuffer();
		try {
			// 创建SSLContext对象，并使用我们指定的信任管理器初始化
			TrustManager[] tm = { new MyX509TrustManager() };
			SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
			sslContext.init(null, tm, new java.security.SecureRandom());
			// 从上述SSLContext对象中得到SSLSocketFactory对象
			SSLSocketFactory ssf = sslContext.getSocketFactory();

			URL url = new URL(requestUrl);
			HttpsURLConnection httpUrlConn = (HttpsURLConnection) url
					.openConnection();
			httpUrlConn.setSSLSocketFactory(ssf);

			httpUrlConn.setDoOutput(true);
			httpUrlConn.setDoInput(true);
			httpUrlConn.setUseCaches(false);
			// 设置请求方式（GET/POST）
			httpUrlConn.setRequestMethod(requestMethod);

			if (Consts.HTTP_REQUEST_METHOD_GET.equalsIgnoreCase(requestMethod))
				httpUrlConn.connect();

			// 当有数据需要提交时
			if (null != formData) {
				OutputStream outputStream = httpUrlConn.getOutputStream();
				// 注意编码格式，防止中文乱码
				outputStream
						.write(formData.getBytes(Consts.CHARACTER_ENCODING));
				outputStream.close();
			}

			// 将返回的输入流转换成字符串
			InputStream inputStream = httpUrlConn.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(
					inputStream, Consts.CHARACTER_ENCODING);
			BufferedReader bufferedReader = new BufferedReader(
					inputStreamReader);

			String str = null;
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			bufferedReader.close();
			inputStreamReader.close();
			// 释放资源
			inputStream.close();
			inputStream = null;
			httpUrlConn.disconnect();
			jsonObject = JSONObject.fromObject(buffer.toString());
		} catch (ConnectException ce) {
			log.error("服务器连接超时 .");
		} catch (Exception e) {
			log.error("HTTP 请求错误 :{}", e);
		}
		return jsonObject;
	}

	/**
	 * 获取access_token的接口地址（GET） 限200（次/天）
	 * 
	 * @param appid
	 *            凭证
	 * @param appsecret
	 *            密钥
	 * @return
	 */
	public static AccessToken getAccessToken(String appid, String appsecret) {
		AccessToken accessToken = null;

		String requestUrl = access_token_url.replace("APPID", appid).replace(
				"APPSECRET", appsecret);
		JSONObject jsonObject = httpRequest(requestUrl,
				Consts.HTTP_REQUEST_METHOD_GET, null);
		// 如果请求成功
		if (null != jsonObject) {
			try {
				accessToken = new AccessToken();
				accessToken.setToken(jsonObject.getString("access_token"));
				accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
			} catch (JSONException e) {
				accessToken = null;
				// 获取token失败
				log.error("获取 token 失败, errcode:{} errmsg:{} .",
						jsonObject.getInt("errcode"),
						jsonObject.getString("errmsg"));
			}
		}
		log.info("获取 accessToken 成功 : token={},expiresIn={} .",
				accessToken.getToken(), accessToken.getExpiresIn());
		return accessToken;
	}

	/**
	 * 创建菜单
	 * 
	 * @param jsonString
	 *            菜单实例
	 * @param accessToken
	 *            有效的access_token
	 * @return 0 : success
	 */
	public static int createMenu(String jsonMenuXml, String accessToken) {
		int result = 0;

		log.info("【创建】自定义菜单 .");
		// 拼装创建菜单的url
		String url = menu_create_url.replace("ACCESS_TOKEN", accessToken);

		log.info("\turl : {}", url);

		// 调用接口创建菜单
		JSONObject jsonObject = httpRequest(url,
				Consts.HTTP_REQUEST_METHOD_POST, jsonMenuXml);

		if (null != jsonObject) {
			if (0 != jsonObject.getInt("errcode")) {
				result = jsonObject.getInt("errcode");
				log.error("创建菜单失败 errcode:{} errmsg:{}.",
						jsonObject.getInt("errcode"),
						jsonObject.getString("errmsg"));
			}
		}

		log.info("【成功】创建自定义菜单 .");
		return result;
	}

	/**
	 * @param jsonString
	 *            json格式菜单字符串
	 * @param appId
	 * @param appSecret
	 * @return
	 */
	public static int createMenu(String jsonString, String appId,
			String appSecret) {
		// 调用接口获取access_token
		AccessToken at = MenuUtils.getAccessToken(appId, appSecret);
		return createMenu(jsonString, at.getToken());
	}

	/**
	 * 从类文件系统中的json文件中获取数据，以创建菜单
	 * 
	 * @param jsonFilePath
	 *            文件路径
	 * @param appId
	 * @param appSecret
	 * @return
	 */
	public static int createMenuFromFileSystemJsonFile(String jsonFilePath,
			String appId, String appSecret) {
		InputStream in = null;
		String jsonMenuString = null;
		try {
			in = new FileInputStream(jsonFilePath);
			jsonMenuString = loadJsonStringFromInputStream(in);
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		} finally {
			if (in != null)
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
		log.info("从 文件系统 加载json菜单文件:");
		log.info("{} 内容:\n{}", jsonFilePath,
				JSONObject.fromObject(jsonMenuString).toString(2));
		return createMenu(jsonMenuString, appId, appSecret);
	}

	/**
	 * 从类路径下的json文件中获取数据，以创建菜单
	 * 
	 * @param jsonFileName
	 *            文件名
	 * @param appId
	 * @param appSecret
	 * @return 0 : success
	 */
	public static int createMenuFromCalssPathJsonFile(String jsonFileName,
			String appId, String appSecret) {
		InputStream in = MenuUtils.class.getClassLoader().getResourceAsStream(
				jsonFileName);
		String jsonMenuString = null;
		try {
			jsonMenuString = loadJsonStringFromInputStream(in);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (in != null)
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
		log.info("从 classpath 加载json菜单文件:");
		log.info("{} 内容:\n{}", jsonFileName,
				JSONObject.fromObject(jsonMenuString).toString(2));
		return createMenu(jsonMenuString, appId, appSecret);
	}

	/**
	 * 从输入流创建菜单<br>
	 * <font color="red">传入的输入流不会在方法内部关闭</font>
	 * 
	 * @param in
	 * @param appId
	 * @param appSecret
	 * @return 0: success
	 */
	public static int createMenuFromInputStream(InputStream in, String appId,
			String appSecret) {

		String jsonMenuString = null;
		try {
			jsonMenuString = loadJsonStringFromInputStream(in);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {

		}
		log.info("从 InputStream 加载json菜单文件:");
		log.info("内容:\n{}", JSONObject.fromObject(jsonMenuString).toString(2));
		return createMenu(jsonMenuString, appId, appSecret);
	}

	private static String loadJsonStringFromInputStream(InputStream in) {
		StringBuilder sb = new StringBuilder();
		InputStreamReader isr = null;
		BufferedReader br = null;
		try {

			isr = new InputStreamReader(in, Consts.CHARACTER_ENCODING);
			br = new BufferedReader(isr);
			String s = null;
			while ((s = br.readLine()) != null) {
				sb.append(s);
			}
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				br.close();
				isr.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return sb.toString();
	}

}
